/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 *
 * http://www.gnu.org/copyleft/gpl.html
 */
package l1j.server.server.datatables;

import java.lang.reflect.Constructor;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
//import java.util.ArrayList;
//import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
//import java.util.Iterator;
import java.util.Map;
import java.util.Set;
//import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import l1j.server.DatabaseFactory;
import l1j.server.server.model.Instance.L1NpcInstance;
import l1j.server.server.templates.L1Npc;
import l1j.server.server.utils.SQLUtil;

public class NpcTable {
	static Logger _log = Logger.getLogger(NpcTable.class.getName());
	public static HashMap<String, Integer> buildFamily() {
		final HashMap<String, Integer> result = new HashMap<String, Integer>();
		Connection con = null;
		PreparedStatement pstm = null;
		ResultSet rs = null;
		try {
			con = DatabaseFactory.get().getConnection();
			pstm = con
					.prepareStatement("select distinct(family) as family from npc WHERE NOT trim(family) =''");
			rs = pstm.executeQuery();
			int id = 1;
			while (rs.next()) {
				final String family = rs.getString("family");
				result.put(family, id++);
			}
		} catch (final SQLException e) {
			_log.log(Level.SEVERE, e.getLocalizedMessage(), e);
		} finally {
			SQLUtil.close(rs);
			SQLUtil.close(pstm);
			SQLUtil.close(con);
		}
		return result;
	}

	private final boolean _initialized;

	private static NpcTable _instance;
	private final HashMap<Integer, L1Npc> _npcs = new HashMap<Integer, L1Npc>();

	private final HashMap<String, Constructor<?>> _constructorCache = new HashMap<String, Constructor<?>>();

	private static final Map<String, Integer> _familyTypes = NpcTable.buildFamily();

	public static NpcTable getInstance() {
		if (_instance == null) {
			_instance = new NpcTable();
		}
		return _instance;
	}

	private NpcTable() {
		this.loadNpcData();
		this._initialized = true;
	}

	public int findNpcIdByName(final String name) {
		for (final L1Npc npc : this._npcs.values()) {
			if (npc.get_name().equals(name)) {
				return npc.get_npcId();
			}
		}
		return 0;
	}

	public int findNpcIdByNameWithoutSpace(final String name) {
		for (final L1Npc npc : this._npcs.values()) {
			if (npc.get_name().replace(" ", "").equals(name)) {
				return npc.get_npcId();
			}
		}
		return 0;
	}

	/**
	 * 用NPCID找尋NPC名稱 opqlo*/
	public String findNpcNameById(int npcid) {
		for (final L1Npc npc : this._npcs.values()) {
			if (npc.get_npcId()==npcid) {
				return npc.get_name();
			}
		}
		return null;
	}

	private Constructor<?> getConstructor(final String implName) {
		try {
			final String implFullName = "l1j.server.server.model.Instance."+ implName + "Instance";
			final Constructor<?> con = Class.forName(implFullName).getConstructors()[0];
			return con;
		} catch (final ClassNotFoundException e) {
			_log.log(Level.WARNING, e.getLocalizedMessage(), e);
		}
		return null;
	}

	public L1Npc getTemplate(final int id) {
		return this._npcs.get(id);
	}

	public boolean isInitialized() {
		return this._initialized;
	}

	private void loadNpcData() {
		Connection con = null;
		PreparedStatement pstm = null;
		ResultSet rs = null;
		try {
			con = DatabaseFactory.get().getConnection();
			pstm = con.prepareStatement("SELECT * FROM npc");
			rs = pstm.executeQuery();
			while (rs.next()) {
				final L1Npc npc = new L1Npc();
				final int npcId = rs.getInt("npcid");
				npc.set_npcId(npcId);
				final String npcname = rs.getString("name");
				npc.set_name(npcname);
				npc.set_nameid(rs.getString("nameid"));
				npc.setImpl(rs.getString("impl"));
				final int gfxid = rs.getInt("gfxid");
				npc.set_gfxid(gfxid);
				npc.set_level(rs.getInt("lvl"));
				npc.set_hp(rs.getInt("hp"));
				npc.set_mp(rs.getInt("mp"));
				npc.set_ac(rs.getInt("ac"));
				npc.set_str(rs.getShort("str"));
				npc.set_con(rs.getShort("con"));
				npc.set_dex(rs.getShort("dex"));
				npc.set_wis(rs.getShort("wis"));
				npc.set_int(rs.getShort("intel"));
				npc.set_mr(rs.getInt("mr"));
				npc.set_exp(rs.getInt("exp"));
				npc.set_lawful(rs.getInt("lawful"));
				//opqlo 改寫大小怪判斷 0=小怪 1=大怪
				npc.set_size(rs.getString("size").contains("large")==true? true:false);
				npc.set_weakAttr(rs.getInt("weakAttr"));
				npc.set_ranged(rs.getInt("ranged"));
				npc.setTamable(rs.getBoolean("tamable"));

				boolean isError = false;//addErrorSpr

				final int movespeed = rs.getInt("passispeed");
				npc.set_passispeed(movespeed);
				 int move =SprTable.getInstance().getMoveSpeed(gfxid, 0);
				if( move > movespeed){
					isError = true;
//					_log.log(Level.SEVERE , npcname +" "+  npcId+" 移動速度不正確:(標準 "+move+" , 目前 "+movespeed);
				}else{
					move = movespeed;
				}
				final int attackspeed = rs.getInt("atkspeed");
				npc.set_atkspeed(attackspeed);
				int attack = SprTable.getInstance().getAttackSpeed(gfxid, 1);
				if(attack > attackspeed){
					isError = true;
//					_log.log(Level.SEVERE , npcname +" "+ npcId+" 攻擊速度不正確:(標準 "+attack+" , 目前 "+attackspeed);
				}else{
					attack = attackspeed;
				}
				npc.setAltAtkSpeed(rs.getInt("alt_atk_speed"));//特殊攻擊速度

				final int amagicspeed = rs.getInt("atk_magic_speed");
				npc.setAtkMagicSpeed(amagicspeed);
				 int amagic = SprTable.getInstance().getDirSpellSpeed(gfxid);
				if( amagic > amagicspeed){
					isError = true;
//					_log.log(Level.SEVERE , npcname +" "+  npcId+" 指向施法速度不正確:(標準 "+amagic+" , 目前 "+amagicspeed);
				}else{
					amagic = amagicspeed;
				}
				final int smagicspeed = rs.getInt("sub_magic_speed");
				npc.setSubMagicSpeed(smagicspeed);
				 int smagic = SprTable.getInstance().getNodirSpellSpeed(gfxid);
				if(smagic > smagicspeed){
					isError = true;
//					_log.log(Level.SEVERE , npcname +" "+  npcId+" 無向施法速度不正確:(標準 "+smagic+" , 目前 "+smagicspeed);
				}else{
					smagic = smagicspeed;
				}
				npc.set_undead(rs.getInt("undead"));
				npc.set_poisonatk(rs.getInt("poison_atk"));
				npc.set_paralysisatk(rs.getInt("paralysis_atk"));
				npc.set_agro(rs.getBoolean("agro"));
				npc.set_agrososc(rs.getBoolean("agrososc"));
				npc.set_agrocoi(rs.getBoolean("agrocoi"));
				final Integer family = _familyTypes.get(rs.getString("family"));
				if (family == null) {
					npc.set_family(0);
				} else {
					npc.set_family(family.intValue());
				}
				final int agrofamily = rs.getInt("agrofamily");
				if (npc.get_family() == 0 && agrofamily == 1) {
					npc.set_agrofamily(0);
				} else {
					npc.set_agrofamily(agrofamily);
				}
				npc.set_agrogfxid1(rs.getInt("agrogfxid1"));
				npc.set_agrogfxid2(rs.getInt("agrogfxid2"));
				npc.set_picupitem(rs.getBoolean("picupitem"));
				npc.set_digestitem(rs.getInt("digestitem"));
				npc.set_bravespeed(rs.getBoolean("bravespeed"));
				int hprtime = rs.getInt("hprinterval");
				if(hprtime >0){
					hprtime /= 1000;
				}
				npc.set_hprinterval(hprtime);
				npc.set_hpr(rs.getInt("hpr"));
				int mprtime = rs.getInt("mprinterval");
				if(mprtime >0){
					mprtime /=1000;
				}
				npc.set_mprinterval(mprtime);
				npc.set_mpr(rs.getInt("mpr"));

				npc.set_teleport(rs.getBoolean("teleport"));
				npc.set_randomlevel(rs.getInt("randomlevel"));
				npc.set_randomhp(rs.getInt("randomhp"));
				npc.set_randommp(rs.getInt("randommp"));
				npc.set_randomac(rs.getInt("randomac"));
				npc.set_randomexp(rs.getInt("randomexp"));
				npc.set_randomlawful(rs.getInt("randomlawful"));
				npc.set_damagereduction(rs.getInt("damage_reduction"));
				npc.set_hard(rs.getBoolean("hard"));
				npc.set_doppel(rs.getBoolean("doppel"));
				npc.set_IsTU(rs.getBoolean("IsTU"));
				npc.set_IsErase(rs.getBoolean("IsErase"));
				npc.setBowActId(rs.getInt("bowActId"));
				npc.setKarma(rs.getInt("karma"));
				npc.setTransformId(rs.getInt("transform_id"));
				npc.setTransformGfxId(rs.getInt("transform_gfxid"));
				npc.setLightSize(rs.getInt("light_size"));
				npc.setAmountFixed(rs.getBoolean("amount_fixed"));
				npc.setChangeHead(rs.getBoolean("change_head"));
				npc.setCantResurrect(rs.getBoolean("cant_resurrect"));
				npc.setExtraDmg(rs.getInt("ExtraDmg"));	//opqlo 額外攻擊力
				npc.setExtraDef(rs.getInt("ExtraDef"));	//opqlo 額外防禦力
				npc.setDmgReduceRate(rs.getByte("DmgReduceRate"));//opqlo 怪物減傷倍率
				this.registerConstructorCache(npc.getImpl());
				this._npcs.put(npcId, npc);
				if(isError){
					int[]speed = {move , attack , amagic , smagic};
					addErrorSpr(npc , speed);
				}
				/**怪物名稱顯示 特殊資訊 opqlo**/
				if(npc.getImpl().equalsIgnoreCase("L1Monster")){
					npc.setSpStatus();
				}
			}
			SprAction();
		} catch (final SQLException e) {
			_log.log(Level.SEVERE, e.getLocalizedMessage(), e);
		} finally {
			SQLUtil.close(rs);
			SQLUtil.close(pstm);
			SQLUtil.close(con);
		}
	}

	public L1NpcInstance newNpcInstance(final int id) {
		final L1Npc npcTemp = this.getTemplate(id);
		if (npcTemp == null) {
			throw new IllegalArgumentException(String.format("NpcTemplate: %d not found", id));
		}
		return this.newNpcInstance(npcTemp);
	}

	public L1NpcInstance newNpcInstance(final L1Npc template) {
		try {
			final Constructor<?> con = this._constructorCache.get(template.getImpl());
			return (L1NpcInstance) con.newInstance(new Object[] { template });
		} catch (final Exception e) {
			_log.log(Level.SEVERE, e.getLocalizedMessage(), e);
		}
		if (template == null) {
			throw new IllegalArgumentException(String.format("NpcTemplate: %d not found", 0));
		}
		//return newNpcInstance(template);//TODO 測試修正空迴圈
		return null;
	}
	private void registerConstructorCache(final String implName) {
		if (implName.isEmpty() || this._constructorCache.containsKey(implName)) {
			return;
		}
		this._constructorCache.put(implName, this.getConstructor(implName));
	}

	private HashMap<L1Npc , int[]>_errorSprList;

	private void addErrorSpr(L1Npc npc , int[] speed){
		if(npc.get_npcId() == 2000208 || npc.get_npcId() == 2000218//封印結界石
				|| npc.get_npcId() == 710065){ //巫術之眼
			return;
		}
		if(npc.getImpl().equalsIgnoreCase("L1Monster")
				|| npc.getImpl().equalsIgnoreCase("L1Doll")){
			if(_errorSprList == null){
				_errorSprList = new HashMap<L1Npc , int[]>();
			}
			if(_errorSprList.equals(npc)){
				return;
			}
			_errorSprList.put(npc , speed);
		}
	}


	private void SprAction(){
		Connection con = null;
		PreparedStatement pstm = null;

		try {
			con = DatabaseFactory.get().getConnection();
			if(_errorSprList == null || _errorSprList.isEmpty()){
				return;
			}
			Set<L1Npc> list = _errorSprList.keySet();
			 Iterator<L1Npc> coll = list.iterator();
			while(coll.hasNext()){
			L1Npc npc = coll.next();
			int[]speed = _errorSprList.get(npc);
			pstm = con.prepareStatement("UPDATE npc SET passispeed=? , atkspeed=? , atk_magic_speed=? , sub_magic_speed=? WHERE npcid=?");
			pstm.setInt(1, speed[0]);
			pstm.setInt(2, speed[1]);
			pstm.setInt(3, speed[2]);
			pstm.setInt(4, speed[3]);
			pstm.setInt(5, npc.get_npcId());
			pstm.execute();
			_log.log(Level.INFO , "修復: "+npc.get_name()+" "+npc.get_npcId()+" 速度異常。");
			}
		} catch (SQLException e) {
			_log.log(Level.SEVERE , e.getLocalizedMessage() , e);
		}finally{
			SQLUtil.close(con);
			SQLUtil.close(pstm);
		}
	}


}
